package com.ibm.dtp.ejb.crud;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.ejb.Init;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

@Stateless
@Local(CrudService.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class CrudServiceBean implements CrudService {
	
	
	@PersistenceContext(unitName = "DTP")
	protected EntityManager em;

	public <T> T create(T t) {
		this.em.persist(t);
		this.em.flush();
		this.em.refresh(t);
		return t;
	}

	public <T, ID extends Serializable> T find(Class<T> type, ID id) {
		return (T) this.em.find(type, id);
	}

	public <T, ID extends Serializable> void delete(Class<T> type, ID id) {
		ID reference = (ID) this.em.getReference(type, id);
		ID ref = reference;
		this.em.remove(ref);
	}

	public <T> T update(T t) {
		return (T) this.em.merge(t);
	}

	public <T> List<T> findWithNamedQuery(String namedQueryName) {
		return this.em.createNamedQuery(namedQueryName).getResultList();
	}

	public <T> List<T> findWithNamedQuery(String namedQueryName, Map parameters) {
		return findWithNamedQuery(namedQueryName, parameters, 0);
	}

	public <T> List<T> findWithNamedQuery(String queryName, int resultLimit) {
		return this.em.createNamedQuery(queryName).setMaxResults(resultLimit)
				.getResultList();
	}

	public <T> List<T> findByNativeQuery(String sql, Class type) {
		return this.em.createNativeQuery(sql, type).getResultList();
	}

	public <T> List<T> findWithNamedQuery(String namedQueryName,
			Map parameters, int resultLimit) {
		Set<Map.Entry> rawParameters = parameters.entrySet();
		Query query = this.em.createNamedQuery(namedQueryName);
		if (resultLimit > 0)
			query.setMaxResults(resultLimit);
		for (Map.Entry entry : rawParameters) {
			query.setParameter((String) entry.getKey(), entry.getValue());
		}
		return query.getResultList();
	}

	@Override
	public <T> List<T> findByQuery(String query) {
		return this.em.createQuery(query).getResultList();
	}

	@Override
	public <T> List<T> findByQuery(String query, int resultLimit) {
		return this.em.createQuery(query).setMaxResults(resultLimit)
				.getResultList();
	}

	@Override
	public <T> List<T> findByQuery(String query, Map parameters) {
		return findByQuery(query, parameters, 0);
	}

	@Override
	public <T> List<T> findByQuery(String queryStr, Map parameters,
			int resultLimit) {
		Set<Map.Entry> rawParameters = parameters.entrySet();
		Query query = this.em.createNamedQuery(queryStr);
		if (resultLimit > 0)
			query.setMaxResults(resultLimit);
		for (Map.Entry entry : rawParameters) {
			query.setParameter((String) entry.getKey(), entry.getValue());
		}
		return query.getResultList();
	}

	@Override
	public <T> List<T> findByDynamicQuery(Class<T> type,
			List<ParameterDefinition> parameters) {
		// generate query
		String queryString = generateQuery(type, parameters);

		Query query = this.em.createQuery(queryString);

		// set parameters
		int k = 1; // parameter position
		for (ParameterDefinition param : parameters) {
			query.setParameter(k++, param.getValue());
		}

		return query.getResultList();
	}

	// helper method
	private <T> String generateQuery(Class<T> type,
			List<ParameterDefinition> parameters) {
		final String VAR = "obj";
		StringBuilder queryBuilder = new StringBuilder(String.format(
				"SELECT %s FROM %s %s", VAR, type.getSimpleName(), VAR));

		if (parameters.size() == 0) { // no conditions
			return queryBuilder.toString();
		}

		// WHERE clause
		Iterator<ParameterDefinition> itr = parameters.iterator();
		int k = 1; // parameter position
		ParameterDefinition firstParam = itr.next(); // always type WITH
		queryBuilder.append(String.format(" WHERE %s.%s = ?%d", VAR, firstParam
				.getName(), k++));

		// append parameters
		for (; itr.hasNext();) {
			ParameterDefinition nextParam = itr.next();
			queryBuilder.append(String.format(" %s %s.%s = ?%s", nextParam
					.getParameterType(), VAR, nextParam.getName(), k++));
		}
		return queryBuilder.toString();
	}
}
